package ch.bfh.fbi.mobiComp.integrator.implementation;

import ch.bfh.fbi.mobiComp.integrator.definition.Integrator;
import ch.bfh.fbi.mobiComp.integrator.definition.PeakListener;

/**
 * The AdaptiveIntegrator is used to silence the noisy sensors. It is programmed to
 * integrate over any-amount sampleSets. If the difference of the amplitude of
 * the integration and newest sample is beyond a certain threshold, the
 * integration is discarded. (usually this situation occurs if some real change
 * is made to the sensor device.
 * 
 * In this implementation a 'Ring-Array' is used to continuously add and remove
 * data without having to (de-)allocate memory.
 * 
 * Complexity: Average calculation: O(1) Add / Remove sample: O(1) Clear
 * Ring-Array: O(1)
 * 
 * @author Quantasy
 * 
 */
public class AdaptiveIntegrator implements Integrator{
	private final double[] buffer;
	private int first;
	private int last;
	private int actualBufferSize = 0;
	private double average;
	private double sum;
	private double maxNoiseError;
	private double lastValue;
	private PeakListener peakListener;

	public AdaptiveIntegrator(final int bufferSize, final double maxNoiseError,
			PeakListener peakListener) throws Exception {
		if (bufferSize < 1) {
			throw new Exception("BufferSize too small");
		}
		this.buffer = new double[bufferSize];
		this.setMaxNoiseError(maxNoiseError);
	}

	/**
	 * @see Integrator
	 * @param maxNoiseError
	 * @throws Exception
	 */
	public synchronized void setMaxNoiseError(final double maxNoiseError)
			throws Exception {
		if (maxNoiseError < 0) {
			throw new Exception("Error cannot be smaller than 0");
		}
		this.maxNoiseError = maxNoiseError;
	}

	/**
	 * @see ch.AdaptiveIntegrator.android.zero.integrator.implementation.Integrator#addSample(int)
	 */
	public synchronized void addSample(final double value) {
		if (Math.abs(Math.abs(value) - Math.abs(this.getAverage())) > this.maxNoiseError) {
			this.peakListener.onPeak(value, this.getAverage());
			this.clear();
		}
		this.lastValue = this.buffer[this.last];
		this.buffer[this.last] = value;
		this.sum += value;
		this.last = (this.last + 1) % this.buffer.length;
		if (this.first != this.last) {
			this.actualBufferSize++;
		} else {
			this.first = (this.last + 1) % this.buffer.length;
			this.sum -= this.lastValue;
		}

	}

	/**
	 * @see ch.AdaptiveIntegrator.android.zero.integrator.implementation.Integrator#clear()
	 */
	public synchronized void clear() {
		this.first = this.last;
		this.actualBufferSize = 0;
		this.sum = 0;
		this.average = 0;
	}

	/**
	 * @see ch.AdaptiveIntegrator.android.zero.integrator.implementation.Integrator#getAverage()
	 */
	public synchronized double getAverage() {
		if (this.actualBufferSize > 0) {
			this.average = (double) ((double) this.sum / (double) this.actualBufferSize);
		}
		return this.average;
	}

	public synchronized double getRoundedAverage() {
		return (double) Math.round(average * 10) / 10;
	}

	public synchronized int getActualBufferSize() {
		return this.actualBufferSize;
	}

}
